'''
    This file is your implementation of a Blokus Playing Agent for the tournament.
    - Change ids to your own.
    - Choose a name for your AI player.
    - Don't forget to create your shapes file by the name specified.
    - Implement the two functions move() and setup().
'''
from GameAgent import GameAgent
from alpha_beta_ours import AlphaBetaSearch, TimeOut
from FakeGameState import FakeGameState
from BlokusGameAction import BlokusGameAction
from time import clock
import BlokusGameConstants as C


def get_sorted_indices(bswb, indices):
    def key(i):
        return bswb/2-abs(bswb / 2 - i / bswb) + bswb/2-abs(bswb / 2 - i % bswb)
    res = list(sorted(indices, key=key, reverse=True))
    #print [(x, key(x)) for x in res]
    return res


class BlokusCompetitionAgent(GameAgent):
    ids = ('036979821', '307753129')  # insert your ID numbers here instead.
    agentName = 'AI_Block_The_Enemy'.format(ids[0][-2:], ids[1][-2:])
                # replace this name with any nice name you choose for your AI player
    shapesFile = 'TournamentShapes.txt'
                    # Create the shapes file used in the tournament by this name
                    # Do not change the name defined here

    def __init__(self, depth=2, a=10000, b=1, c=500, isfactor=False, ordered=False):
        self.depth = depth
        self.states = {}
        self.a = a
        self.b = b
        self.c = c
        self.d = {}
        self.isfactor = isfactor
        self.name = 'ours(depth={}, a={}, b={}, ordered={})'.format(depth, a, b, ordered)
        self.order = get_sorted_indices if ordered else lambda b, s: s

    def setup(self, player, state, timeLimit):
        self.start = clock()
        self.player = player
        self.timeLimit = timeLimit
        self.numshapes = len(state.currentPlayer.getCurrentColor().shapes)
        if timeLimit != C.NO_LIMIT:
            self.alphaBeta = AlphaBetaSearch(self.player, self.utility, self.noMoreTime)
            self.move = self.timed_move
            self.turnTimeLimit = (timeLimit - 5) / state.numOfShapes
            self.timeLimit -= 1
            self.depth = 0
        else:
            self.alphaBeta = AlphaBetaSearch(self.player, self.utility)
        self.timePlayed = clock() - self.start

    def hurry_up(self, gameState):
        self.startTime = clock()
        n = len(gameState.currentPlayer.getCurrentColor().shapes)
        time_left = (self.startTime - self.start) - self.timeLimit
        if abs(time_left)/(n or 1) < 0.00000001:
            #print 'yes'
            return True, next(FakeGameState(gameState).get_successors(self.order))[0]
        return False, None

    def timed_move(self, gameState):
        hurry, value = self.hurry_up(gameState)
        if hurry:
            return value
        n = len(gameState.currentPlayer.getCurrentColor().shapes)
        if n == 5:
            self.depth = 4
        elif self.numshapes - n == 3:
            self.depth = 2
        try:
            bestAction, value = self.alphaBeta.search(self.depth, gameState, self.order)  # returns (action, value) 
        except TimeOut:
            pass
        timePlayed = clock() - self.startTime
#        print timePlayed
        self.timePlayed += timePlayed
        return bestAction

    def move(self, gameState):
        hurry, value = self.hurry_up(gameState)
        if hurry:
            return value
        res, _ = self.alphaBeta.search(self.depth, gameState, self.order)
        timePlayed = clock() - self.startTime
#       print timePlayed
        self.timePlayed += timePlayed
        return res

    def utility(self, gameState):
        state = FakeGameState(gameState)
        pl1 = state.currentPlayer
        pl2 = state.opponentPlayer
        sc1 = pl1.getScore()
        sc2 = pl2.getScore()
        if state.is_win() is None:
            return [-float('inf'), 0, float('inf')][cmp(sc1, sc2) + 1]

        factor = 1 if self.player == state.getCurrentPlayer() else -1
        A = self.a * (sc1 - sc2)

        x = state.count_all_uccessors(pl1)
        rx = state.count_all_uccessors(pl2)
        B = (factor if self.isfactor else 1) * self.b * (x - rx)
        return A + B

    def noMoreTime(self):
        return clock() > self.startTime + self.turnTimeLimit
